/*
 * Copyright (C) 2012 by Hao Hou<ghost89413@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>*
 */
#include <userconf.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <stdarg.h>
#define HashNum 4097
typedef struct
{  
	ConfTypeDescriptor type;
	union
	{ 
		uint32_t i;
		char	 s[128];
	} value;
} Data;
typedef struct _HashNode
{
	uint32_t hash[4];
	Data data;
	struct _HashNode* next;
} HashNode;
struct _UserConfigure
{
	HashNode* hash[HashNum];
};

void __user_conf_hash__(const char* str, int len, uint32_t* h)
{
	const uint32_t m = 0xc6a4a793;
	const unsigned char* data = (unsigned char*) str;
	h[0] = 0x5f2c2345^(len*m);  //Murmur
	h[1] = 0;	   	    //sum
	h[2] = 0x23f4537;   	    //muliply
	h[3] = 0;	   	    //ELF Hash
	int i = 0;
	for(i = 0; i < len; i ++)
	{
		if((i&3) == 0 && i != 0)
		{
			unsigned int k = *(unsigned int*)(data + i - 4);
			h[0] += k;
			h[0] *= m;
			h[0] ^= h[0]>>16;
		}
		h[1] = (h[1] + data[i] + (data[i]<<17) + (data[i]>>15))%0xfffe7985UL;
		h[2] = (data[i] * h[2])%0xee6b27ebUL;
		h[3] = (h[3] << 4) + data[i];
		uint32_t g = h[3] & 0xf0000000ul;
		if(g) h[3]^=g>>24;
		h[3] &= ~g;
	}
	switch(len&3)
	{
	case 3:

		h[0] += data[len-3] << 16;
	case 2:
		h[0] += data[len-2] << 8;
	case 1: 
		h[0] += data[len-1];
		h[0] *= m;
		h[0] ^= h[0] >> 16;
	}
	h[0] *= m;
	h[0] ^= h[0]>>10;
	h[0] *= m;
	h[0] ^= h[0]>>17;
}
HashNode* __user_conf_search__(UserConfigure* conf,const char* str, int len, int creat)
{
	uint32_t hash[4];
	__user_conf_hash__(str,len,hash);
	int slot = hash[0]%HashNum;
	HashNode* p = conf->hash[slot];
	while(p && 
	      !(p->hash[0] == hash[0] &&
	        p->hash[1] == hash[1] &&
	        p->hash[2] == hash[2] &&
	        p->hash[3] == hash[3])) p = p->next;
	if(creat && NULL == p)
	{
		p = (HashNode*)malloc(sizeof(HashNode));
		memset(p,0,sizeof(HashNode));
		p->next = conf->hash[slot];
		conf->hash[slot] = p;
		p->hash[0] = hash[0];
		p->hash[1] = hash[1];
		p->hash[2] = hash[2];
		p->hash[3] = hash[3];
	}
	return p;
}
UserConfigure* CreateConfig()
{
	UserConfigure* ret = (UserConfigure*)malloc(sizeof(UserConfigure));
	if(NULL == ret) return NULL;
	memset(ret,0,sizeof(UserConfigure));
	return ret;
}
int ParseConfig(UserConfigure* conf, const char* filename)
{
	char buffer[1024];
	FILE* fp = NULL;
	do{
		fp = fopen(filename,"r");
		if(NULL == fp) break;
		int lc = 0;
		int error = 0;
		while(fgets(buffer, 1024, fp) && !error)
		{
			lc ++;
			unsigned char* p;
			int status = 0;
			int slen = 0;
			char key[1024];
			int klen = 0;
			HashNode* node;
			for(p = (unsigned char*)buffer; *p && !error; p ++)
			{
				if(*p == '\n' || *p == '\r') continue;
				if(0 == status) //parse key
				{
					if(*p == ' ' || *p == '\t') continue;
					if(*p == '=') 
					{
						status = 1; 
						continue;
					}
					else if(*p == '#') 
					{
						status = -2;
						continue;
					}
					key[klen++] = *p;
				}
				else if(1 == status) //parse value
				{
					key[klen] = 0;
					node = __user_conf_search__(conf,key,klen,1);
					if(NULL == node)
					{
						error = 1;
						continue;
					}
					if(*p == ' ' || *p == '\t') continue;
					else if(*p >= '1' && *p <= '9')
					{
						status = 2; //dec
						node->data.type = Integer;
						node->data.value.i = *p - '0';
					}
					else if(*p == '0')
					{
						status = 3; //otc/hex
						node->data.type = Integer;
						node->data.value.i = 0;
					}
					else if(*p == '"')
					{
						status = 4;//string
						node->data.type = String;
					}
				}
				else if(2 == status) //dec
				{
					if(*p >= '0' && *p <= '9')
						node->data.value.i = 10*node->data.value.i+*p-'0';
					else if(*p == ' ' || *p == '\t') status = -1;
					else if(*p == '#') status = -2;
					else error = 2;
				}
				else if(3 == status) //hex/dec
				{
					if(*p >= '0' && *p <= '7')
					{
						status = 5; // dec
						node->data.value.i = *p - '0';
					}
					else if(*p == 'x' || *p == 'X')
					{
						status = 6; // hex
					}
					else if(*p == ' ' || *p == '\t') status = -1;
					else if(*p == '#') status = -2;
					else error = 2;
				}
				else if(4 == status) //string
				{
					if(*p == '"') status = -1; // string end
					else if(*p == '\\') status = 8; // escape
					else 
						node->data.value.s[slen ++] = *p;
				}
				else if(5 == status) //oct
				{
					if(*p >= '0' && *p <= '7')
						node->data.value.i=8*node->data.value.i+*p-'0';
					else if(*p == ' ' || *p == '\t') status = -1;
					else if(*p == '#') status = -2;
					else error = 2;
				}
				else if(6 == status) //hex
				{
					if(*p >= '0' && *p <= '9')
						node->data.value.i=16*node->data.value.i+*p-'0';
					else if(*p >= 'a' && *p <= 'f')
						node->data.value.i=16*node->data.value.i+*p-'a'+ 10;
					else if(*p == ' ' || *p == '\t') status = -1;
					else if(*p == '#') status = -2;
					else error = 2;
				}
				else if(8 == status)
				{
					char tmp = 0;
					if(*p == 'n') tmp = '\n';
					else if(*p == 't') tmp = '\t';
					else tmp = *p;
					node->data.value.s[slen++] = tmp;
					status = 4;
				}
				else if(-1 == status)
				{
					if(*p == '#') status = -2;
					else if(*p != ' ' && *p != '\t') error = 2;
				}
			}
			if(error == 1) break;
			else if(error == 2)
			{
				VINE_LOG_WARNING("Invalid line in file %s(%d)", filename, lc);
				error = 0;
			}
		}
		if(error) break;
		VINE_LOG_NOTICE("user configure %s loaded successfully",filename);
		fclose(fp);
		return 0;
	}while(0);
	VINE_LOG_ERROR("Parsing Config File Error");
	if(fp) fclose(fp);
	return -1;
}
void* FetchValue(UserConfigure* conf,ConfTypeDescriptor type, void* p, void* def, const char* key)
{
	if(NULL == p) return NULL;
	HashNode* node = __user_conf_search__(conf,key,strlen(key),0);
	if(node)
	{
		if(type != node->data.type)
		{
			VINE_LOG_WARNING("type mismatch variable:%s, use default value",key);
			*(void**)p = def;
			if(type == Integer)
				VINE_LOG_DEBUG("%s = %d",key,**(int**)p);
			else if(type == String)
				VINE_LOG_DEBUG("%s = \"%s\"",key,*(char**)p);
			return def;
		}
		*(void**)p = &(node->data.value);
		if(type == Integer)
			VINE_LOG_DEBUG("%s = %d",key,**(int**)p);
		else if(type == String)
			VINE_LOG_DEBUG("%s = \"%s\"",key,*(char**)p);
		return *(void**)p;
	}
	VINE_LOG_WARNING("no such key %s, use default value",key);
	*(void**)p = def;
	if(type == Integer)
		VINE_LOG_DEBUG("%s = %d",key,**(int**)p);
	else if(type == String)
		VINE_LOG_DEBUG("%s = \"%s\"",key,*(char**)p);
	return def;
}
void* FetchValueEx(UserConfigure* conf,ConfTypeDescriptor type,
		   void* p, void* def, const char* key_template, ...)
{
	char keyname[1024];
	va_list ap;
	va_start(ap,key_template);
	vsnprintf(keyname,1024,key_template,ap);
	va_end(ap);
	return FetchValue(conf,type,p,def,keyname);
}
int SetValue(UserConfigure* conf, ConfTypeDescriptor type, const char* key, void* p)
{
	if(NULL == conf)
	{
		VINE_LOG_ERROR("conf should not be NULL!");
		return -1;
	}
	HashNode* node = __user_conf_search__(conf,key,strlen(key),1);
	if(NULL == node) return -1;
	node->data.type = type;
	if(Integer == type)
		node->data.value.i = *(int*)p;
	if(String == type)
		strncpy(node->data.value.s, (const char*)p, 128);
	return 0;
}
int  SetValueEx(UserConfigure* conf, ConfTypeDescriptor type,
		const char* key_template, void* p, ...)
{
	char keyname[1024];
	va_list ap;
	va_start(ap,p);
	vsnprintf(keyname,1024,key_template,ap);
	va_end(ap);
	return SetValue(conf,type,keyname,p);
}
void DestoryConf(UserConfigure* conf)
{
	int i;
	for(i = 0; i < HashNum; i ++)
	{
		HashNode* p = conf->hash[i];
		while(p)
		{
			void* tmp = p;
			p = p->next;
			free(tmp);
		}
	}
	if(NULL != conf) free(conf);
}
